Water quality parameters retrieval using Remote Sensing data - Part
01
The use of remote sensing data to predict water quality variables
that are optically active (i.e., interacts with light) has been applied
to ocean and coastal waters for ~50 years. Thanks to the new generation
of sensors with adequate spectral, radiometric and spatial resolution
(i.e., Landsat, Sentinel-2, etc) in the last 15 years the community
started to use RS to freshwater studies.
Remote sensing allow us to predict some WQ parameters: Suspended
sediments, chlorophyl-a, phycocianin, dissolved organic matter, carbon,
secchi disk depth, turbidity…It is an important source of data that
could help biologists, limnologists, and all the aquatic science
community into understanding of water pattern.
In this Notebook, we gonna learn how to use Remote Sensing data to
predict water quality variables (Chl-a and Suspended Sediments). For
that, we gonna use a hyperspectral dataset for global freshwater
(GLORIA) to simulated Landsat and Sentinel-2 bands (i.e., makes the in
situ data the “same” as satellite see). After that, we gonna do some
exploratory analysis and filters to remove outliers and develop a
empirical and Machine Learning algorithm (Random Forest) to predict
Chl-a and TSS for satellite images.
The processing flow is divided into three topics:
- Installing the packages, downloading the data, simulate the bands
and removing outliers (pre-processing step)
- Model development (train, validation, and full model
development)
- Model application: application of the algorithms to satellite data
using STAC and Google Earth Engine
#install.packages(c('data.table',
# 'dplyr',
# 'rstac',
# 'terra',
# 'raster',
# 'mapview',
# 'Metrics',
# 'geodrawr',
# 'svDialogs',
# 'PerformanceAnalytics',
# 'rstac',
# 'wesanderson'))
require(data.table)
require(dplyr)
require(rstac)
require(terra)
require(mapview)
require(httr)
require(Metrics)
require(geodrawr)
require(svDialogs)
require(rstac)
require(wesanderson)
require(PerformanceAnalytics)
Downloading GLORIA data
The GLORIA dataset is a compilation of remote sensing and water
quality data for global waters, with dedicated data for freshwater
ecosystems. It is free and available for everyone, and covers most part
of the globe with more than 7,000 samples (Figure 01)
For more information, users are refered to the publication (Lehmann et
al. 2023), the dataset in PANGAEA
and the Nature
Earth and Environmment blog post
In the following codes, we are gonna:
- Create the necessary folders
- Download the data from PANGAEA
- Extract it in R
# Let's download the GLORIA to a empty folder called "Data" in our project
## First, create directories
dir.create('Data')
Warning: 'Data' already exists
dir.create("Outputs")
Warning: 'Outputs' already exists
dir.create("Scripts")
Warning: 'Scripts' already exists
## URL
URL = 'https://download.pangaea.de/dataset/948492/files/GLORIA-2022.zip'
# Before download, let`s set timeout to 200s (sometimes PANGAEA download is slow)
options(timeout=300)
# If the directory with files doesn't exist, let's download GLORIA data.
if(dir.exists('Data/GLORIA_2022/') == FALSE) {
# Download
download.file(URL, 'Data/GLORIA.zip')
# Extract
unzip(zipfile = 'Data/GLORIA.zip', exdir = 'Data')
}
After downloading the data, we can explore it. It is composed of
various sheets, such as:
- Remote sensing reflectance
- Water quality variables and other auxiliary data
- Radiometric quantities (e.g., water leaving radiance, sky
radiance)
- Quality control flags (e.g., suspected spectra are flagged and could
be removed by the user)
- Uncertantity table (mean and standard deviation of provided
data)
We reccomend the user to open the table in Excel or Google Docs and
explore it. In this exercise, we gonna use the following tables:
- GLORIA_meta_and_lab.csv
- GLORIA_Rrs.csv
Let’s remember that Remote Sensing Reflectance is the ratio between
water leaving radiance and downwelling irradiance, compensated by the
sky radiance and corrected by glint effects (Equation 01).
\[\begin{equation}
R_{rs} = (L_t - (L_{sky}*\rho))/E_s
\end{equation}\]
These tables contain all the necessary variables we need to produce
the desired algorithms. Let`s now open and explore it. You gonna see
that the collumn GLORIA_ID contains the ID of each unique station. This
collumn will be used in the next steps to merge both tables.
## See the dataset
# META and Lab data
meta_and_lab = fread("Data/GLORIA_2022/GLORIA_meta_and_lab.csv")
head(meta_and_lab)
NA
NA
# Rrs data
rrs = fread("Data/GLORIA_2022/GLORIA_Rrs.csv")
head(rrs)
NA
Simulation of satellite bands
However, this is a hiperspectral dataset. If we want to develop RS
models, we will need to simulate (i.e. integrate based on the Relative
Spectral Response) each band of the desiered sensor
To do that, we gonna use the bandSimulation package available in
GitHub.
devtools::install_github("dmaciel123/BandSimulation")
Skipping install of 'bandSimulation' from a github remote, the SHA1 (88ed39b8) has not changed since last install.
Use `force = TRUE` to force installation
require(bandSimulation)
## Simulation for Sentinel-2 MSI sensor
spectra_formated = select(rrs, paste("Rrs_", 400:900, sep = '')) %>% t()
head(spectra_formated[1:10,1:10])
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
Rrs_400 0.001032099 0.0009919781 0.001176673 0.001045943 0.001123216 0.001262030 0.001502713 0.001159079 0.001598394 0.002235938
Rrs_401 0.001026443 0.0009886549 0.001172247 0.001044805 0.001121460 0.001260533 0.001500229 0.001153895 0.001589690 0.002231068
Rrs_402 0.001025443 0.0009907777 0.001173282 0.001049251 0.001125342 0.001264954 0.001506565 0.001155018 0.001587384 0.002237669
Rrs_403 0.001042929 0.0010105485 0.001195316 0.001072244 0.001149169 0.001291754 0.001539501 0.001176772 0.001613440 0.002284503
Rrs_404 0.001032327 0.0009995005 0.001184033 0.001062849 0.001139232 0.001281843 0.001521784 0.001162394 0.001597356 0.002260299
Rrs_405 0.001033530 0.0010029657 0.001187121 0.001068868 0.001144698 0.001288210 0.001528472 0.001164688 0.001597279 0.002268107
MSI_sim = msi_simulation(spectra = spectra_formated,
point_name = rrs$GLORIA_ID)
#It simulates for Sentinel-2A and Sentinel-2B and gives the results in a list.
# Let's select only Sentinel-2A.
MSI = MSI_sim$s2a[,-1] %>% t() %>% data.frame()
head(MSI[,1:9])
# Add names to a collumn
MSI$GLORIA_ID = row.names(MSI)
#Check final
head(MSI[,1:9])
NA
NA
NA
NA
Checking the results and understanding what is “Band
Simulation”
When we simulate a satellite band, we are compensating for the
differences in detector sensibility to each wavelength. The Figure below
shows differences in the spectral response function for Sentinel-2A/MSI,
Landsat-8/OLI and Landsat-7/ETM+. You can note that relative spectral
response values close to “1” indicates that the detector can measure (or
detect) all the radiance in this wavelength. A sensor band is composed
by an interval of these wavelengths and then, the simulated band is the
integral the R[rs] considering the Relative Spectral Response curve, or
Equation 02.
\[\begin{equation}
R_{rs} = \frac{\int_{n}^{m} SRF(\lambda) R_{rs} dx}{\int_{n}^{m}
SRF(\lambda)}
\end{equation}\]
# Change band names
names(MSI) = c('x440', "x490", 'x560', 'x660', "x705", 'x740', 'x780', 'x850', 'x865', "GLORIA_ID")
ROW = 150
# Example
matplot(t(select(rrs, paste("Rrs_", 400:900, sep = ''))[ROW,]), ylim = c(0,0.015),
x= c(400:900), pch = 20, xlab = '', ylab = '')
par(new=T)
matplot(t(MSI[ROW,-10]), x= c(440,490,560,660,705,740,780,850,860), pch = 20,
ylim = c(0,0.015), xlim = c(400,900), col = 'red', cex = 2, xlab = 'Wavelength (nm)',
ylab = 'Rrs (sr-1)')

NA
NA
## Merge with water quality, lat long (By GLORIA_ID)
merged = merge(select(meta_and_lab, c('GLORIA_ID', 'Chla', "TSS", "Latitude", "Longitude")),
MSI, by = "GLORIA_ID")
head(merged)
NA
#vector = vect(merged,
# geom = c('Longitude', 'Latitude'),
# "EPSG:4326")
#mapview(sf::st_as_sf(vector), zcol = 'TSS')
Finalizing the exploratory analyze
Before continuing and saving the simulated data, we will need to
remove No-data values, perform some filter process (e.g., remove very
low values of Chl-a) and also calculate some indexes that could help
into modeling process.
Let’s do that.
# We want to model TSS and Chla concentration based on RF
merged = merged[is.na(merged$Chla) == FALSE, ]
merged = merged[is.na(merged$TSS) == FALSE, ]
head(merged)
## Index calculation:
merged$NDCI = (merged$x705-merged$x660)/(merged$x705+merged$x660)+1
merged$Blue_red = (merged$x490-merged$x660)/(merged$x490+merged$x660)+1
merged$green_blue = (merged$x560-merged$x490)/(merged$x560+merged$x490)+1
merged$nir_red = (merged$x740-merged$x660)/(merged$x740+merged$x660)+1
# Check dimension
dim(merged)
[1] 3203 18
Performing some exploratory analyzes.
chart.Correlation(merged[,-1])

NA
NA
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpgYGB7cn0KCmBgYAoKIyBXYXRlciBxdWFsaXR5IHBhcmFtZXRlcnMgcmV0cmlldmFsIHVzaW5nIFJlbW90ZSBTZW5zaW5nIGRhdGEgLSBQYXJ0IDAxCgoKVGhlIHVzZSBvZiByZW1vdGUgc2Vuc2luZyBkYXRhIHRvIHByZWRpY3Qgd2F0ZXIgcXVhbGl0eSB2YXJpYWJsZXMgdGhhdCBhcmUgb3B0aWNhbGx5IGFjdGl2ZSAoaS5lLiwgaW50ZXJhY3RzIHdpdGggbGlnaHQpIGhhcyBiZWVuIGFwcGxpZWQgdG8gb2NlYW4gYW5kIGNvYXN0YWwgd2F0ZXJzIGZvciB+NTAgeWVhcnMuIFRoYW5rcyB0byB0aGUgbmV3IGdlbmVyYXRpb24gb2Ygc2Vuc29ycyB3aXRoIGFkZXF1YXRlIHNwZWN0cmFsLCByYWRpb21ldHJpYyBhbmQgc3BhdGlhbCByZXNvbHV0aW9uIChpLmUuLCBMYW5kc2F0LCBTZW50aW5lbC0yLCBldGMpIGluIHRoZSBsYXN0IDE1IHllYXJzIHRoZSBjb21tdW5pdHkgc3RhcnRlZCB0byB1c2UgUlMgdG8gZnJlc2h3YXRlciBzdHVkaWVzLiAKCgpSZW1vdGUgc2Vuc2luZyBhbGxvdyB1cyB0byBwcmVkaWN0IHNvbWUgV1EgcGFyYW1ldGVyczogU3VzcGVuZGVkIHNlZGltZW50cywgY2hsb3JvcGh5bC1hLCBwaHljb2NpYW5pbiwgZGlzc29sdmVkIG9yZ2FuaWMgbWF0dGVyLCBjYXJib24sIHNlY2NoaSBkaXNrIGRlcHRoLCB0dXJiaWRpdHkuLi5JdCBpcyBhbiBpbXBvcnRhbnQgc291cmNlIG9mIGRhdGEgdGhhdCBjb3VsZCBoZWxwIGJpb2xvZ2lzdHMsIGxpbW5vbG9naXN0cywgYW5kIGFsbCB0aGUgYXF1YXRpYyBzY2llbmNlIGNvbW11bml0eSBpbnRvIHVuZGVyc3RhbmRpbmcgb2Ygd2F0ZXIgcGF0dGVybi4gCgoKCkluIHRoaXMgTm90ZWJvb2ssIHdlIGdvbm5hIGxlYXJuIGhvdyB0byB1c2UgUmVtb3RlIFNlbnNpbmcgZGF0YSB0byBwcmVkaWN0IHdhdGVyIHF1YWxpdHkgdmFyaWFibGVzIChDaGwtYSBhbmQgU3VzcGVuZGVkIFNlZGltZW50cykuIEZvciB0aGF0LCB3ZSBnb25uYSB1c2UgYSBoeXBlcnNwZWN0cmFsIGRhdGFzZXQgZm9yIGdsb2JhbCBmcmVzaHdhdGVyIChHTE9SSUEpIHRvIHNpbXVsYXRlZCBMYW5kc2F0IGFuZCBTZW50aW5lbC0yIGJhbmRzIChpLmUuLCBtYWtlcyB0aGUgaW4gc2l0dSBkYXRhIHRoZSAic2FtZSIgYXMgc2F0ZWxsaXRlIHNlZSkuIEFmdGVyIHRoYXQsIHdlIGdvbm5hIGRvIHNvbWUgZXhwbG9yYXRvcnkgYW5hbHlzaXMgYW5kIGZpbHRlcnMgdG8gcmVtb3ZlIG91dGxpZXJzIGFuZCBkZXZlbG9wIGEgZW1waXJpY2FsIGFuZCBNYWNoaW5lIExlYXJuaW5nIGFsZ29yaXRobSAoUmFuZG9tIEZvcmVzdCkgdG8gcHJlZGljdCBDaGwtYSBhbmQgVFNTIGZvciBzYXRlbGxpdGUgaW1hZ2VzLiAKClRoZSBwcm9jZXNzaW5nIGZsb3cgaXMgZGl2aWRlZCBpbnRvIHRocmVlIHRvcGljczoKCjEuIEluc3RhbGxpbmcgdGhlIHBhY2thZ2VzLCBkb3dubG9hZGluZyB0aGUgZGF0YSwgc2ltdWxhdGUgdGhlIGJhbmRzIGFuZCByZW1vdmluZyBvdXRsaWVycyAocHJlLXByb2Nlc3Npbmcgc3RlcCkKMi4gTW9kZWwgZGV2ZWxvcG1lbnQgKHRyYWluLCB2YWxpZGF0aW9uLCBhbmQgZnVsbCBtb2RlbCBkZXZlbG9wbWVudCkKMy4gTW9kZWwgYXBwbGljYXRpb246IGFwcGxpY2F0aW9uIG9mIHRoZSBhbGdvcml0aG1zIHRvIHNhdGVsbGl0ZSBkYXRhIHVzaW5nIFNUQUMgYW5kIEdvb2dsZSBFYXJ0aCBFbmdpbmUgCgoKCmBgYHtyfQoKI2luc3RhbGwucGFja2FnZXMoYygnZGF0YS50YWJsZScsCiMgICAgICAgICAgICAgICAgICAgICAgICAnZHBseXInLAojICAgICAgICAgICAgICAgICAgICAgICAgJ3JzdGFjJywKIyAgICAgICAgICAgICAgICAgICAgICAgICd0ZXJyYScsCiMgICAgICAgICAgICAgICAgICAgICAgICAncmFzdGVyJywKIyAgICAgICAgICAgICAgICAgICAgICAgICdtYXB2aWV3JywKIyAgICAgICAgICAgICAgICAgICAgICAgICdNZXRyaWNzJywKIyAgICAgICAgICAgICAgICAgICAgICAgICdnZW9kcmF3cicsCiMgICAgICAgICAgICAgICAgICAgICAgICAnc3ZEaWFsb2dzJywKIyAgICAgICAgICAgICAgICAgICAgICAgICAnUGVyZm9ybWFuY2VBbmFseXRpY3MnLAojICAgICAgICAgICAgICAgICAgICAgICAgJ3JzdGFjJywKIyAgICAgICAgICAgICAgICAgICAgICAgICd3ZXNhbmRlcnNvbicpKQoKCnJlcXVpcmUoZGF0YS50YWJsZSkKcmVxdWlyZShkcGx5cikKcmVxdWlyZShyc3RhYykKcmVxdWlyZSh0ZXJyYSkKcmVxdWlyZShtYXB2aWV3KQpyZXF1aXJlKGh0dHIpCnJlcXVpcmUoTWV0cmljcykKcmVxdWlyZShnZW9kcmF3cikKcmVxdWlyZShzdkRpYWxvZ3MpCnJlcXVpcmUocnN0YWMpCnJlcXVpcmUod2VzYW5kZXJzb24pCnJlcXVpcmUoUGVyZm9ybWFuY2VBbmFseXRpY3MpCgoKCmBgYAojIERvd25sb2FkaW5nIEdMT1JJQSBkYXRhCgpUaGUgR0xPUklBIGRhdGFzZXQgaXMgYSBjb21waWxhdGlvbiBvZiByZW1vdGUgc2Vuc2luZyBhbmQgd2F0ZXIgcXVhbGl0eSBkYXRhIGZvciBnbG9iYWwgd2F0ZXJzLCB3aXRoIGRlZGljYXRlZCBkYXRhIGZvciBmcmVzaHdhdGVyIGVjb3N5c3RlbXMuIEl0IGlzIGZyZWUgYW5kIGF2YWlsYWJsZSBmb3IgZXZlcnlvbmUsIGFuZCBjb3ZlcnMgbW9zdCBwYXJ0IG9mIHRoZSBnbG9iZSB3aXRoIG1vcmUgdGhhbiA3LDAwMCBzYW1wbGVzIChGaWd1cmUgMDEpCgohW0ZpZ3VyZSAwMV0oaHR0cHM6Ly9lYXJ0aGVudmlyb25tZW50Y29tbXVuaXR5Lm5hdHVyZS5jb20vY2RuLWNnaS9pbWFnZS9tZXRhZGF0YT1jb3B5cmlnaHQsZml0PXNjYWxlLWRvd24sZm9ybWF0PWF1dG8sc2hhcnBlbj0xLHF1YWxpdHk9OTUvaHR0cHM6Ly9pbWFnZXMuemFwbml0by5jb20vdXBsb2Fkcy9oaUNNT3ByblR0U0NUSk52NzhndV9sb2NhdGlvbnMuanBnKQoKCkZvciBtb3JlIGluZm9ybWF0aW9uLCB1c2VycyBhcmUgcmVmZXJlZCB0byB0aGUgcHVibGljYXRpb24gWyhMZWhtYW5uIGV0IGFsLiAyMDIzKV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE1OTctMDIzLTAxOTczLXkpLCB0aGUgZGF0YXNldCBpbiBbUEFOR0FFQV0oaHR0cDovL2h0dHBzOi8vZG9pLnBhbmdhZWEuZGUvMTAuMTU5NC9QQU5HQUVBLjk0ODQ5MikgYW5kIHRoZSBbTmF0dXJlIEVhcnRoIGFuZCBFbnZpcm9ubW1lbnQgYmxvZyBwb3N0XShodHRwOi8vaHR0cHM6Ly9lYXJ0aGVudmlyb25tZW50Y29tbXVuaXR5Lm5hdHVyZS5jb20vcG9zdHMvZ2xvcmlhLWNoYWxsZW5nZXMtaW4tZGV2ZWxvcGluZy1hLWdsb2JhbGx5LXJlcHJlc2VudGF0aXZlLWh5cGVyc3BlY3RyYWwtaW4tc2l0dS1kYXRhc2V0LWZvci10aGUtcmVtb3RlLXNlbnNpbmctb2Ytd2F0ZXItcmVzb3VyY2VzKQoKSW4gdGhlIGZvbGxvd2luZyBjb2Rlcywgd2UgYXJlIGdvbm5hOgoKMS4gQ3JlYXRlIHRoZSBuZWNlc3NhcnkgZm9sZGVycwoyLiBEb3dubG9hZCB0aGUgZGF0YSBmcm9tIFBBTkdBRUEKMy4gRXh0cmFjdCBpdCBpbiBSCgoKYGBge3J9CgojIExldCdzIGRvd25sb2FkIHRoZSBHTE9SSUEgdG8gYSBlbXB0eSBmb2xkZXIgY2FsbGVkICJEYXRhIiBpbiBvdXIgcHJvamVjdAoKIyMgRmlyc3QsIGNyZWF0ZSBkaXJlY3RvcmllcwoKZGlyLmNyZWF0ZSgiRGF0YSIpCmRpci5jcmVhdGUoIk91dHB1dHMiKQpkaXIuY3JlYXRlKCJTY3JpcHRzIikKCiMjIFVSTApVUkwgPSAnaHR0cHM6Ly9kb3dubG9hZC5wYW5nYWVhLmRlL2RhdGFzZXQvOTQ4NDkyL2ZpbGVzL0dMT1JJQS0yMDIyLnppcCcKCiMgQmVmb3JlIGRvd25sb2FkLCBsZXRgcyBzZXQgdGltZW91dCB0byAyMDBzIChzb21ldGltZXMgUEFOR0FFQSBkb3dubG9hZCBpcyBzbG93KQoKb3B0aW9ucyh0aW1lb3V0PTMwMCkKCiMgSWYgdGhlIGRpcmVjdG9yeSB3aXRoIGZpbGVzIGRvZXNuJ3QgZXhpc3QsIGxldCdzIGRvd25sb2FkIEdMT1JJQSBkYXRhLgoKaWYoZGlyLmV4aXN0cygnRGF0YS9HTE9SSUFfMjAyMi8nKSA9PSBGQUxTRSkgewogIAogICAgICAjIERvd25sb2FkCiAgICAgIGRvd25sb2FkLmZpbGUoVVJMLCAnRGF0YS9HTE9SSUEuemlwJykKICAgICAgCiAgICAgICMgRXh0cmFjdAogICAgICB1bnppcCh6aXBmaWxlID0gJ0RhdGEvR0xPUklBLnppcCcsIGV4ZGlyID0gJ0RhdGEnKQogICAgICAKICAgICAgCn0KCgoKCmBgYAoKQWZ0ZXIgZG93bmxvYWRpbmcgdGhlIGRhdGEsIHdlIGNhbiBleHBsb3JlIGl0LiBJdCBpcyBjb21wb3NlZCBvZiB2YXJpb3VzIHNoZWV0cywgc3VjaCBhczoKCjEuIFJlbW90ZSBzZW5zaW5nIHJlZmxlY3RhbmNlIAoyLiBXYXRlciBxdWFsaXR5IHZhcmlhYmxlcyBhbmQgb3RoZXIgYXV4aWxpYXJ5IGRhdGEKMy4gUmFkaW9tZXRyaWMgcXVhbnRpdGllcyAoZS5nLiwgd2F0ZXIgbGVhdmluZyByYWRpYW5jZSwgc2t5IHJhZGlhbmNlKQo0LiBRdWFsaXR5IGNvbnRyb2wgZmxhZ3MgKGUuZy4sIHN1c3BlY3RlZCBzcGVjdHJhIGFyZSBmbGFnZ2VkIGFuZCBjb3VsZCBiZSByZW1vdmVkIGJ5IHRoZSB1c2VyKQo1LiBVbmNlcnRhbnRpdHkgdGFibGUgKG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBwcm92aWRlZCBkYXRhKQoKV2UgcmVjY29tZW5kIHRoZSB1c2VyIHRvIG9wZW4gdGhlIHRhYmxlIGluIEV4Y2VsIG9yIEdvb2dsZSBEb2NzIGFuZCBleHBsb3JlIGl0LiBJbiB0aGlzIGV4ZXJjaXNlLCB3ZSBnb25uYSB1c2UgdGhlIGZvbGxvd2luZyB0YWJsZXM6CgoxLiBHTE9SSUFfbWV0YV9hbmRfbGFiLmNzdgoyLiBHTE9SSUFfUnJzLmNzdgoKTGV0J3MgcmVtZW1iZXIgdGhhdCBSZW1vdGUgU2Vuc2luZyBSZWZsZWN0YW5jZSBpcyB0aGUgcmF0aW8gYmV0d2VlbiB3YXRlciBsZWF2aW5nIHJhZGlhbmNlIGFuZCBkb3dud2VsbGluZyBpcnJhZGlhbmNlLCBjb21wZW5zYXRlZCBieSB0aGUgc2t5IHJhZGlhbmNlIGFuZCBjb3JyZWN0ZWQgYnkgZ2xpbnQgZWZmZWN0cyAoRXF1YXRpb24gMDEpLgoKClxiZWdpbntlcXVhdGlvbn0KUl97cnN9ID0gKExfdCAtIChMX3tza3l9KlxyaG8pKS9FX3MKXGVuZHtlcXVhdGlvbn0KCgpUaGVzZSB0YWJsZXMgY29udGFpbiBhbGwgdGhlIG5lY2Vzc2FyeSB2YXJpYWJsZXMgd2UgbmVlZCB0byBwcm9kdWNlIHRoZSBkZXNpcmVkIGFsZ29yaXRobXMuIExldGBzIG5vdyBvcGVuIGFuZCBleHBsb3JlIGl0LiBZb3UgZ29ubmEgc2VlIHRoYXQgdGhlIGNvbGx1bW4gR0xPUklBX0lEIGNvbnRhaW5zIHRoZSBJRCBvZiBlYWNoIHVuaXF1ZSBzdGF0aW9uLiBUaGlzIGNvbGx1bW4gd2lsbCBiZSB1c2VkIGluIHRoZSBuZXh0IHN0ZXBzIHRvIG1lcmdlIGJvdGggdGFibGVzLgoKYGBge3J9CgoKIyMgU2VlIHRoZSBkYXRhc2V0CgojIE1FVEEgYW5kIExhYiBkYXRhCgptZXRhX2FuZF9sYWIgPSBmcmVhZCgiRGF0YS9HTE9SSUFfMjAyMi9HTE9SSUFfbWV0YV9hbmRfbGFiLmNzdiIpCmhlYWQobWV0YV9hbmRfbGFiKQoKCmBgYAoKYGBge3J9CgojIFJycyBkYXRhCgpycnMgPSBmcmVhZCgiRGF0YS9HTE9SSUFfMjAyMi9HTE9SSUFfUnJzLmNzdiIpCmhlYWQocnJzKQoKYGBgCgojIFNpbXVsYXRpb24gb2Ygc2F0ZWxsaXRlIGJhbmRzCgoKSG93ZXZlciwgdGhpcyBpcyBhIGhpcGVyc3BlY3RyYWwgZGF0YXNldC4gSWYgd2Ugd2FudCB0byBkZXZlbG9wIFJTIG1vZGVscywgd2Ugd2lsbCBuZWVkIHRvIHNpbXVsYXRlIChpLmUuIGludGVncmF0ZSBiYXNlZCBvbiB0aGUgUmVsYXRpdmUgU3BlY3RyYWwgUmVzcG9uc2UpIGVhY2ggYmFuZCBvZiB0aGUgZGVzaWVyZWQgc2Vuc29yCgpUbyBkbyB0aGF0LCB3ZSBnb25uYSB1c2UgdGhlIGJhbmRTaW11bGF0aW9uIHBhY2thZ2UgYXZhaWxhYmxlIGluIEdpdEh1Yi4gCgpgYGB7cn0KCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiZG1hY2llbDEyMy9CYW5kU2ltdWxhdGlvbiIpCgpyZXF1aXJlKGJhbmRTaW11bGF0aW9uKQoKYGBgCgpgYGB7cn0KCiMjIFNpbXVsYXRpb24gZm9yIFNlbnRpbmVsLTIgTVNJIHNlbnNvciAKCgpzcGVjdHJhX2Zvcm1hdGVkID0gc2VsZWN0KHJycywgcGFzdGUoIlJyc18iLCA0MDA6OTAwLCBzZXAgPSAnJykpICU+JSB0KCkKCmhlYWQoc3BlY3RyYV9mb3JtYXRlZFsxOjEwLDE6MTBdKQoKTVNJX3NpbSA9IG1zaV9zaW11bGF0aW9uKHNwZWN0cmEgPSBzcGVjdHJhX2Zvcm1hdGVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50X25hbWUgPSBycnMkR0xPUklBX0lEKQoKCiNJdCBzaW11bGF0ZXMgZm9yIFNlbnRpbmVsLTJBIGFuZCBTZW50aW5lbC0yQiBhbmQgZ2l2ZXMgdGhlIHJlc3VsdHMgaW4gYSBsaXN0LgojIExldCdzIHNlbGVjdCBvbmx5IFNlbnRpbmVsLTJBLgoKTVNJID0gTVNJX3NpbSRzMmFbLC0xXSAlPiUgdCgpICU+JSBkYXRhLmZyYW1lKCkKCmhlYWQoTVNJWywxOjldKQoKCiMgQWRkIG5hbWVzIHRvIGEgY29sbHVtbgpNU0kkR0xPUklBX0lEID0gcm93Lm5hbWVzKE1TSSkKCiNDaGVjayBmaW5hbCAKCiNoZWFkKE1TSVssMTo5XSkKCgoKCmBgYAoKIyBDaGVja2luZyB0aGUgcmVzdWx0cyBhbmQgdW5kZXJzdGFuZGluZyB3aGF0IGlzICJCYW5kIFNpbXVsYXRpb24iIAoKV2hlbiB3ZSBzaW11bGF0ZSBhIHNhdGVsbGl0ZSBiYW5kLCB3ZSBhcmUgY29tcGVuc2F0aW5nIGZvciB0aGUgZGlmZmVyZW5jZXMgaW4gZGV0ZWN0b3Igc2Vuc2liaWxpdHkgdG8gZWFjaCB3YXZlbGVuZ3RoLiBUaGUgRmlndXJlIGJlbG93IHNob3dzIGRpZmZlcmVuY2VzIGluIHRoZSBzcGVjdHJhbCByZXNwb25zZSBmdW5jdGlvbiBmb3IgU2VudGluZWwtMkEvTVNJLCBMYW5kc2F0LTgvT0xJIGFuZCBMYW5kc2F0LTcvRVRNKy4gWW91IGNhbiBub3RlIHRoYXQgcmVsYXRpdmUgc3BlY3RyYWwgcmVzcG9uc2UgdmFsdWVzIGNsb3NlIHRvICIxIiBpbmRpY2F0ZXMgdGhhdCB0aGUgZGV0ZWN0b3IgY2FuIG1lYXN1cmUgKG9yIGRldGVjdCkgYWxsIHRoZSByYWRpYW5jZSBpbiB0aGlzIHdhdmVsZW5ndGguIEEgc2Vuc29yIGJhbmQgaXMgY29tcG9zZWQgYnkgYW4gaW50ZXJ2YWwgb2YgdGhlc2Ugd2F2ZWxlbmd0aHMgYW5kIHRoZW4sIHRoZSBzaW11bGF0ZWQgYmFuZCBpcyB0aGUgaW50ZWdyYWwgdGhlIFJbcnNdIGNvbnNpZGVyaW5nIHRoZSBSZWxhdGl2ZSBTcGVjdHJhbCBSZXNwb25zZSBjdXJ2ZSwgb3IgRXF1YXRpb24gMDIuCgoKIVtGaWd1cmUgMDJdKGh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvNy83ZC9TcGVjdHJhbF9yZXNwb25zZXNfb2ZfTGFuZHNhdF83X0VUTSUyQiUyQ19MYW5kc2F0XzhfT0xJX2FuZF9TZW50aW5lbF8yX01TSV9pbl90aGVfdmlzaWJsZV9hbmRfbmVhcl9pbmZyYXJlZC5wbmcpCgpcYmVnaW57ZXF1YXRpb259ClJfe3JzfSA9ICBcZnJhY3tcaW50X3tufV57bX0gU1JGKFxsYW1iZGEpIFJfe3JzfSBkeH17XGludF97bn1ee219IFNSRihcbGFtYmRhKX0gClxlbmR7ZXF1YXRpb259CgpgYGB7cn0KIyBDaGFuZ2UgYmFuZCBuYW1lcwoKbmFtZXMoTVNJKSA9IGMoJ3g0NDAnLCAieDQ5MCIsICd4NTYwJywgJ3g2NjAnLCAieDcwNSIsICd4NzQwJywgJ3g3ODAnLCAneDg1MCcsICd4ODY1JywgIkdMT1JJQV9JRCIpCgpST1cgPSAxNTAKCiMgRXhhbXBsZQoKbWF0cGxvdCh0KHNlbGVjdChycnMsIHBhc3RlKCJScnNfIiwgNDAwOjkwMCwgc2VwID0gJycpKVtST1csXSksIHlsaW0gPSBjKDAsMC4wMTUpLAogICAgICAgIHg9IGMoNDAwOjkwMCksIHBjaCA9IDIwLCB4bGFiID0gJycsIHlsYWIgPSAnJykKCnBhcihuZXc9VCkKCm1hdHBsb3QodChNU0lbUk9XLC0xMF0pLCB4PSBjKDQ0MCw0OTAsNTYwLDY2MCw3MDUsNzQwLDc4MCw4NTAsODYwKSwgcGNoID0gMjAsCiAgICAgICAgeWxpbSA9IGMoMCwwLjAxNSksIHhsaW0gPSBjKDQwMCw5MDApLCBjb2wgPSAncmVkJywgY2V4ID0gMiwgeGxhYiA9ICdXYXZlbGVuZ3RoIChubSknLCAKICAgICAgICB5bGFiID0gJ1JycyAoc3ItMSknKQoKCmBgYAoKCgpgYGB7cn0KCiMjIE1lcmdlIHdpdGggd2F0ZXIgcXVhbGl0eSwgbGF0IGxvbmcgKEJ5IEdMT1JJQV9JRCkKCm1lcmdlZCA9IG1lcmdlKHNlbGVjdChtZXRhX2FuZF9sYWIsIGMoJ0dMT1JJQV9JRCcsICdDaGxhJywgIlRTUyIsICJMYXRpdHVkZSIsICJMb25naXR1ZGUiKSksCiAgICAgICAgICAgICAgIE1TSSwgYnkgPSAiR0xPUklBX0lEIikKCmhlYWQobWVyZ2VkKQoKYGBgCgoKYGBge3J9CgojdmVjdG9yID0gdmVjdChtZXJnZWQsIAojICAgICAgICAgICAgICBnZW9tID0gYygnTG9uZ2l0dWRlJywgJ0xhdGl0dWRlJyksIAojICAgICAgICAgICAgICAiRVBTRzo0MzI2IikKCgojbWFwdmlldyhzZjo6c3RfYXNfc2YodmVjdG9yKSwgIHpjb2wgPSAnVFNTJykKCmBgYAoKIyBGaW5hbGl6aW5nIHRoZSBleHBsb3JhdG9yeSBhbmFseXplCgpCZWZvcmUgY29udGludWluZyBhbmQgc2F2aW5nIHRoZSBzaW11bGF0ZWQgZGF0YSwgd2Ugd2lsbCBuZWVkIHRvIHJlbW92ZSBOby1kYXRhIHZhbHVlcywgcGVyZm9ybSBzb21lIGZpbHRlciBwcm9jZXNzIChlLmcuLCByZW1vdmUgdmVyeSBsb3cgdmFsdWVzIG9mIENobC1hKSBhbmQgYWxzbyBjYWxjdWxhdGUgc29tZSBpbmRleGVzIHRoYXQgY291bGQgaGVscCBpbnRvIG1vZGVsaW5nIHByb2Nlc3MuIAoKTGV0J3MgZG8gdGhhdC4KCgpgYGB7cn0KCiMgV2Ugd2FudCB0byBtb2RlbCBUU1MgYW5kIENobGEgY29uY2VudHJhdGlvbiBiYXNlZCBvbiBSRgoKbWVyZ2VkID0gbWVyZ2VkW2lzLm5hKG1lcmdlZCRDaGxhKSA9PSBGQUxTRSwgXQptZXJnZWQgPSBtZXJnZWRbaXMubmEobWVyZ2VkJFRTUykgPT0gRkFMU0UsIF0KCiNoZWFkKG1lcmdlZCkKCgojIyBJbmRleCBjYWxjdWxhdGlvbjoKCm1lcmdlZCRORENJID0gKG1lcmdlZCR4NzA1LW1lcmdlZCR4NjYwKS8obWVyZ2VkJHg3MDUrbWVyZ2VkJHg2NjApKzEKbWVyZ2VkJEJsdWVfcmVkID0gKG1lcmdlZCR4NDkwLW1lcmdlZCR4NjYwKS8obWVyZ2VkJHg0OTArbWVyZ2VkJHg2NjApKzEKbWVyZ2VkJGdyZWVuX2JsdWUgPSAobWVyZ2VkJHg1NjAtbWVyZ2VkJHg0OTApLyhtZXJnZWQkeDU2MCttZXJnZWQkeDQ5MCkrMQptZXJnZWQkbmlyX3JlZCA9IChtZXJnZWQkeDc0MC1tZXJnZWQkeDY2MCkvKG1lcmdlZCR4NzQwK21lcmdlZCR4NjYwKSsxCgoKIyBDaGVjayBkaW1lbnNpb24KCmRpbShtZXJnZWQpCgpgYGAKClBlcmZvcm1pbmcgc29tZSBleHBsb3JhdG9yeSBhbmFseXplcy4KCgpgYGB7cn0KCmNoYXJ0LkNvcnJlbGF0aW9uKG1lcmdlZFssLTFdKQoKCmBgYAoK